package create

import (
	"context"
	"fmt"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// 给document新增字段
func AddField(mongo *mongo.Database, ctx context.Context) {
	// 转换成objectid的类型，否则查询不到的
	objectid, err := primitive.ObjectIDFromHex("62b07d4ac1e231479c955df6")
	if err != nil {
		fmt.Println(err)
	}
	filter := bson.M{"objectid": objectid} // 查询的条件，相当于mysql的where后面的条件
	// 据官方文档描述，$set是$addFields在4.2版本的别名，但实测addFields无法在下列语句中使用，说明他们还是不一样的。目前用起来，set挺好。具体看文档：https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#mongodb-pipeline-pipe.-addFields
	updateField := bson.M{"$set": bson.M{"intArray": []int32{1, 2, 3, 4, 5, 6}}} // set字段表示新增字段，可以有多个字段，如果该字段已存在，则会覆盖原来的值
	result, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// 设置Upsert，就会在没有查询到对应文档时，新增一个文档，且查询字段会被作为其中一个属性插入
func AddFieldWithUpsert(mongo *mongo.Database, ctx context.Context) {
	// 转换成objectid的类型，否则查询不到的
	objectid, err := primitive.ObjectIDFromHex("62b07d4ac1e231479c955df1")
	if err != nil {
		fmt.Println(err)
	}
	filter := bson.M{"objectid": objectid} // 查询的条件，相当于mysql的where后面的条件
	// 据官方文档描述，$set是$addFields在4.2版本的别名，但实测addFields无法在下列语句中使用，说明他们还是不一样的。目前用起来，set挺好。具体看文档：https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#mongodb-pipeline-pipe.-addFields
	updateField := bson.M{"$set": bson.M{"intArray": []int32{1, 2, 3, 4, 5, 6}}} // set字段表示新增字段，可以有多个字段，如果该字段已存在，则会覆盖原来的值
	upsert := true
	updateOption := options.UpdateOptions{Upsert: &upsert}
	result, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField, &updateOption)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
	fmt.Println("插入文档的id:", result.UpsertedID) // 返回新增文档的_id值
}

// 给字段增加子字段
func AddChildField(mongo *mongo.Database, ctx context.Context) {

	// 这里仅支持给object类型增加子字段，直接使用.即可
	updateField := bson.M{"$set": bson.M{"object.child": bson.A{bson.M{"text1": "child1"}, bson.M{"text2": "child2"}, bson.M{"text3": "child3"}}}} // set字段表示新增字段，可以有多个字段，如果该字段已存在，则会覆盖原来的值
	result, err := mongo.Collection("test").UpdateOne(ctx, bson.M{}, updateField)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// 如果数组元素是object，那么可以使用下面方法给其中一个元素新增一个字段，如果满足条件的元素有好多个，只会给第一个元素新增字段
func AddArrayChildField(mongo *mongo.Database, ctx context.Context) {

	// 这种方法必须将需要新增字段的数组包含在查询条件中，否则会报错，例如下面的object.child，child就是需要新增字段的数组
	filter := bson.M{"object.child": bson.M{"$type": 3}}
	// $[elem] 与arrayFilter配合，起到下标匹配的效果，elem就是下标的代指
	updateField := bson.M{"$set": bson.M{"object.child.$.text4": "child4"}} // set字段表示新增字段，可以有多个字段，如果该字段已存在，则会覆盖原来的值
	result, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// 如果数组元素是object，那么可以使用下面方法给每个元素增加一个新的字段text4
func AddArrayChildAllField(mongo *mongo.Database, ctx context.Context) {

	filter := bson.M{} // 查询的条件，相当于mysql的where后面的条件
	// $[elem] 与arrayFilter配合，起到下标匹配的效果，elem就是下标的代指。具体见：https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered/
	updateField := bson.M{"$set": bson.M{"object.child.$[elem].text4": "child4"}} // set字段表示新增字段，可以有多个字段，如果该字段已存在，则会覆盖原来的值
	arrayFilter := options.ArrayFilters{Filters: bson.A{bson.M{"elem.text4": bson.M{"$exists": false}}}}
	updateOption := options.UpdateOptions{ArrayFilters: &arrayFilter}
	result, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField, &updateOption)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// replaceWith能够将指定的字段提升为根文档，也替换掉原来的根文档，该操作也创建了一个新的文档。
func AddFieldWithReplaceWith(mongo *mongo.Database, ctx context.Context) {

	// replaceWith是replaceRoot的别名，与上面的set来源相同。具体看文档：https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#mongodb-pipeline-pipe.-replaceWith
	// replaceWith关键字是使文档中的某个object字段，替换掉该文档，使其成为一个文档，类型必须是object
	// 且所有匹配了查询条件的文档，必须包含该字段，否则也会报错。
	updateOption := bson.A{bson.M{"$replaceWith": "$object"}}
	result, err := mongo.Collection("test").UpdateMany(ctx, bson.M{}, updateOption)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// 该方法是在上一方法的基础上，增加了一个类似默认值的项，如果某个文档没有匹配到对应的字段，则会用默认值创建文档，且源文档还是会被删除
func AddFieldWithMergeObjects(mongo *mongo.Database, ctx context.Context) {

	//mergeObjects实际作用是合并多个文档，且放在后面的文档会覆盖前面的文档，
	// 所以，当我们需要replaceWith的文档没有object字段时，就会因为合并了前面的文档，而达到类似于默认值的效果
	// 从而避免了文档不包括该字段会报错的问题
	// 注意"$object"要放在后面
	updateOption := bson.A{bson.M{"$replaceWith": bson.M{"$mergeObjects": bson.A{bson.M{"text": "默认值"}, "$object"}}}}
	result, err := mongo.Collection("test").UpdateMany(ctx, bson.M{}, updateOption)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// 上一种方法还有个替代方案
func AddFieldWithIfNull(mongo *mongo.Database, ctx context.Context) {

	// ifNull如字面意思，判断是否存在object，如果不存在，则返回谋面的内容作为结果，和上面的方法一样的效果
	updateOption := bson.A{bson.M{"$replaceWith": bson.M{"$ifNull": bson.A{"$object", bson.M{"_id": "$_id", "missingObject": true}}}}}
	result, err := mongo.Collection("test").UpdateMany(ctx, bson.M{}, updateOption)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}

// 上一方法也还有不足，因为可能没有所匹配的字段就不需要被替换呢？所以还可以通过加判断条件的方法去避免报错
func AddFieldWithFind(mongo *mongo.Database, ctx context.Context) {

	// 给UpdateMany加一些筛选条件，避免出现不存在object字段的问题
	findField := bson.M{"object": bson.M{"$exists": true, "$type": 3}}

	updateField := bson.A{bson.M{"$replaceWith": "$object"}}
	result, err := mongo.Collection("test").UpdateMany(ctx, findField, updateField)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("匹配文档的数量:", result.MatchedCount)
	fmt.Println("修改文档的数量:", result.MatchedCount)
}
